package service

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"inventory/api/inventory"
	"inventory/internal/constant"
	"inventory/pkg/orm"
	"inventory/pkg/util"
	"strconv"
	"strings"
	"time"

	"inventory/internal/model"
	"inventory/internal/pkg/generator"
	"inventory/pkg/ecode"
)

// GetScheduleIn 调入管理列表
func (s *Service) GetScheduleIn(ctx context.Context, req *inventory.GetScheduleInRequest) (resp *inventory.GetScheduleInResponse, err error) {
	var (
		total              int64
		endDate, startDate time.Time
		scheduleOrders     []*model.ScheduleOrder
		views              []*inventory.ScheduleInView
		outWarehouses      []*model.Warehouse
	)
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if startDate, endDate, err = verifyStartDateAndEndDate(req.StartDate, req.EndDate); err != nil {
		return
	}
	if _, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.VisitSchedulingOrder); err != nil {
		return
	}
	if total, scheduleOrders, err = s.dao.FindScheduleOrders(ctx, sgId, req.Status, req.Id, req.OutWarehouseId,
		startDate, endDate, int(req.PageNo), int(req.PageSize), req.QueryOrderNumber); err != nil {
		return
	}
	outWarehouseIDMap := make(util.UInt32BoolMap)
	for _, o := range scheduleOrders {
		outWarehouseIDMap[o.OutWarehouseID] = true
	}
	if outWarehouses, err = s.dao.FindWarehousesByIDs(ctx, outWarehouseIDMap.GetKeys(), sgId); err != nil {
		return
	}
	outWarehouseNameMap := make(map[uint32]string)
	for _, w := range outWarehouses {
		outWarehouseNameMap[w.ID] = w.Name
	}
	views = make([]*inventory.ScheduleInView, len(scheduleOrders))
	for i, o := range scheduleOrders {
		views[i] = &inventory.ScheduleInView{
			OrderNumber:      o.OrderNumber,
			ApplyTime:        o.ApplyTime.Format(util.DefaultLayout),
			OutWarehouseName: outWarehouseNameMap[o.OutWarehouseID],
			DetailCount:      o.DetailCount,
			Status:           uint32(o.Status),
		}
		if o.ReceiveTime != nil {
			views[i].ReceiveTime = o.ReceiveTime.Format(util.DefaultLayout)
		}
	}

	resp = &inventory.GetScheduleInResponse{Orders: views, Total: uint32(total)}
	return
}

// GetScheduleOut 调出管理列表
func (s *Service) GetScheduleOut(ctx context.Context, req *inventory.GetScheduleOutRequest) (resp *inventory.GetScheduleOutResponse, err error) {
	var (
		total              int64
		endDate, startDate time.Time
		scheduleOrders     []*model.ScheduleOrder
		views              []*inventory.ScheduleOutView
		inWarehouses       []*model.Warehouse
		auditOrderIDs      []uint64
		orderStorageStatus util.UInt64BoolMap
	)
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if startDate, endDate, err = verifyStartDateAndEndDate(req.StartDate, req.EndDate); err != nil {
		return
	}
	if _, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.VisitSchedulingOrder); err != nil {
		return
	}
	if total, scheduleOrders, err = s.dao.FindScheduleOrders(
		ctx, sgId, req.Status, req.InWarehouseId, req.Id,
		startDate, endDate, int(req.PageNo), int(req.PageSize), req.QueryOrderNumber); err != nil {
		return
	}
	inWarehouseMap := make(util.UInt32BoolMap)
	inWarehouseNameMap := make(map[uint32]string)
	orderStorageStatus = make(util.UInt64BoolMap)
	if len(scheduleOrders) > 0 {
		for _, o := range scheduleOrders {
			inWarehouseMap[o.InWarehouseID] = true
			if o.Status == constant.ScheduleOrderCreated {
				auditOrderIDs = append(auditOrderIDs, o.ID)
			}
		}
		if inWarehouses, err = s.dao.FindWarehousesByIDs(ctx, inWarehouseMap.GetKeys(), sgId); err != nil {
			return
		}
		for _, w := range inWarehouses {
			inWarehouseNameMap[w.ID] = w.Name
		}
		var (
			details  []*model.ScheduleDetail
			storages []*model.Storage
		)
		if len(auditOrderIDs) > 0 {
			storageKeyMap := make(util.StringBoolMap)
			if details, err = s.dao.FindScheduleOrderDetails(ctx, auditOrderIDs); err != nil {
				return
			}
			for _, d := range details {
				storageKeyMap[d.StorageKey] = true
				orderStorageStatus[d.OrderID] = true
			}
			if storages, err = s.dao.FindWarehouseStoragesByStorageKeys(ctx, req.Id, storageKeyMap.GetKeys()); err != nil {
				return
			}
			storageMap := make(model.StorageMap)
			storageMap.InitStorageMap(storages)
			for _, d := range details {
				if storage, ok := storageMap[d.StorageKey]; ok {
					if d.ApplyQuantity > storage.Quantity-storage.FreezeQuantity {
						orderStorageStatus[d.OrderID] = false
					}
				} else {
					orderStorageStatus[d.OrderID] = false
				}
			}
		}
	}
	views = make([]*inventory.ScheduleOutView, len(scheduleOrders))
	for i, o := range scheduleOrders {
		var storageStatus int
		if o.Status == constant.ScheduleOrderCreated {
			if !orderStorageStatus[o.ID] {
				storageStatus = 1
			}
		}
		views[i] = &inventory.ScheduleOutView{
			OrderNumber:     o.OrderNumber,
			ApplyTime:       o.ApplyTime.Format(util.DefaultLayout),
			InWarehouseName: inWarehouseNameMap[o.InWarehouseID],
			DetailCount:     o.DetailCount,
			Status:          uint32(o.Status),
			StorageStatus:   uint32(storageStatus), // 库存状态，0~充足，1~不足
		}
		if o.SendTime != nil {
			views[i].SendTime = o.SendTime.Format(util.DefaultLayout)
		}
	}
	resp = &inventory.GetScheduleOutResponse{Orders: views, Total: uint32(total)}
	return
}

// GetScheduleInfo 获取调拨单详情
func (s *Service) GetScheduleInfo(ctx context.Context, req *inventory.CommonOrderRequest) (resp *inventory.GetScheduleInfoResponse, err error) {
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if _, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.VisitSchedulingOrder); err != nil {
		return
	}
	var (
		scheduleDetails []*model.ScheduleDetail
		views           []*inventory.ScheduleInfoView
		storages        []*model.Storage
		scheduleOrder   *model.ScheduleOrder
		warehouses      []*model.Warehouse
	)
	if scheduleOrder, err = s.dao.GetScheduleOrder(ctx, sgId, req.OrderNumber); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = constant.ErrOrderNotExist
		}
		return
	}
	if warehouses, err = s.dao.FindWarehousesByIDs(
		ctx, []uint32{scheduleOrder.InWarehouseID, scheduleOrder.OutWarehouseID}, sgId); err != nil {
		return
	}
	warehouseMap := make(map[uint32]*model.Warehouse)
	for _, w := range warehouses {
		warehouseMap[w.ID] = w
	}
	if scheduleDetails, err = s.dao.FindScheduleOrderDetails(ctx, []uint64{scheduleOrder.ID}); err != nil {
		return
	}
	storageKeyMap := make(util.StringBoolMap)
	for _, d := range scheduleDetails {
		storageKeyMap[d.StorageKey] = true
	}
	if storages, err = s.dao.FindWarehouseStoragesByStorageKeys(
		ctx, scheduleOrder.OutWarehouseID, storageKeyMap.GetKeys()); err != nil {
		return
	}
	storageMap := make(model.StorageMap)
	storageMap.InitStorageMap(storages)
	views = make([]*inventory.ScheduleInfoView, len(scheduleDetails))
	for i, d := range scheduleDetails {
		r := model.StorageSnapShotRemark{}
		if err = json.Unmarshal(d.Remark, &r); err != nil {
			return
		}
		var storageQuantity, freezeQuantity float64
		if storage, ok := storageMap[d.StorageKey]; ok {
			storageQuantity = storage.Quantity
			freezeQuantity = storage.FreezeQuantity
		}
		views[i] = &inventory.ScheduleInfoView{
			StorageType:     inventory.StorageType(d.StorageType),
			StorageKey:      d.StorageKey,
			ApplyQuantity:   d.ApplyQuantity,
			SendQuantity:    d.SendQuantity,
			ReceiveQuantity: d.ReceiveQuantity,
			StorageQuantity: storageQuantity,
			FreezeQuantity:  freezeQuantity,
			Name:            r.Name,
			SkuName:         r.SkuName,
			Code:            r.Code,
			TypeName:        r.Type,
			Unit:            r.Unit,
			StorageUnit:     r.StorageUnit,
			StorageRatio:    r.StorageRatio,
			Price:           d.Price,
		}

	}
	resp = &inventory.GetScheduleInfoResponse{
		OrderNumber:      scheduleOrder.OrderNumber,
		InWarehouseName:  warehouseMap[scheduleOrder.InWarehouseID].Name,
		OutWarehouseName: warehouseMap[scheduleOrder.OutWarehouseID].Name,
		ApplyTime:        scheduleOrder.ApplyTime.Format(util.DefaultLayout),
		Applier:          scheduleOrder.ApplierName,
		Receiver:         scheduleOrder.ReceiverName,
		Reviewer:         scheduleOrder.ReviewerName,
		DetailCount:      scheduleOrder.DetailCount,
		Status:           uint32(scheduleOrder.Status),
		Data:             views,
	}
	if scheduleOrder.SendTime != nil {
		resp.SendTime = scheduleOrder.SendTime.Format(util.DefaultLayout)
	}
	if scheduleOrder.ReceiveTime != nil {
		resp.ReceiveTime = scheduleOrder.ReceiveTime.Format(util.DefaultLayout)
	}
	return
}

// ApplySchedule 创建调拨申请单
func (s *Service) ApplySchedule(ctx context.Context, req *inventory.ApplyScheduleRequest) (resp *inventory.CommonOrderReply, err error) {
	var (
		objectKeys                     []string
		materialIDs                    []uint64
		remark                         *model.FullRemark
		remarkJson                     json.RawMessage
		objectRemarks, materialRemarks map[string]*model.FullRemark
		priceMap                       map[string]uint64
	)
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if len(req.Details) == 0 {
		err = ecode.BadRequest("明细数量不允许为0")
		return
	}
	if req.Id == req.OutWarehouseId {
		err = ecode.BadRequest("不支持仓内调拨")
		return
	}
	// group ids
	storageKeyMap := make(util.StringBoolMap)
	for _, d := range req.Details {
		if d.Quantity <= 0 {
			err = ecode.BadRequest("申请数量必须大于0")
			return
		}
		storageKeyMap[d.StorageKey] = true
		switch d.StorageType {
		case inventory.StorageType_MATERIAL:
			if tmpId, e := strconv.Atoi(strings.Split(d.StorageKey, "-")[1]); e != nil {
				err = ecode.BadRequest("物料Storage_key格式错误")
				return
			} else {
				materialIDs = append(materialIDs, uint64(tmpId))
			}
		default:
			objectKeys = append(objectKeys, d.StorageKey)
		}
	}
	// build remarks
	if len(objectKeys) != 0 {
		if objectRemarks, err = s.downstream.BuildObjectRemarks(ctx, sgId, objectKeys); err != nil {
			return
		}
		if len(objectRemarks) != len(objectKeys) {
			err = ecode.BadRequest("请求中存在无效的商品SKU")
			return
		}
		for _, o := range objectRemarks {
			if o.PStoreID != 0 {
				err = ecode.BadRequest("所选商品为门店自建商品，不支持调拨")
				return
			}
		}
		var boms []*model.BomConfig
		if boms, err = s.dao.FindBomByStorageKeys(ctx, objectKeys); err != nil {
			return
		}
		if len(boms) != 0 {
			objNameMap := make(util.StringBoolMap)
			for _, bom := range boms {
				objNameMap[objectRemarks[bom.StorageKey].Name] = true
			}
			err = ecode.AppendMessage(constant.ErrForbiddenAction,
				fmt.Sprintf("所选商品(%s)已配置bom，不能进行盘点", strings.Join(objNameMap.GetKeys(), ",")))
			return
		}
	}
	if len(materialIDs) != 0 {
		if materialRemarks, err = s.dao.BuildMaterialsRemarks(ctx, sgId, materialIDs); err != nil {
			return
		}
		if len(materialRemarks) != len(materialIDs) {
			err = ecode.BadRequest("请求中存在无效的产品ID")
			return
		}
		for _, o := range materialRemarks {
			if o.PStoreID != 0 {
				err = ecode.BadRequest("所选产品为门店自建产品，不支持调拨")
				return
			}
		}
	}
	resp = &inventory.CommonOrderReply{}
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		if _, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.ScheduleIn); err != nil {
			return
		}
		// 调出仓库不受门店模式限制，验证为品牌内可用仓库即可
		if _, err = s.dao.GetWarehouseInfo(ctx, sgId, 0, req.OutWarehouseId); err != nil {
			return ecode.BadRequest("无效的调出仓库ID")
		}
		now := time.Now()
		if resp.OrderNumber, err = generator.GenerateOrderNumber(now, constant.OrderTypeSchedule, empId); err != nil {
			return err
		}
		if priceMap, err = s.dao.GetLastMonthStoragePriceByStorageKeys(ctx, req.OutWarehouseId, storageKeyMap.GetKeys(),
			now, materialRemarks, constant.UnitTypeStorage); err != nil {
			return
		}
		total := 0
		for _, d := range req.Details {
			price, _ := priceMap[d.StorageKey]
			if d.StorageType == inventory.StorageType_MATERIAL {
				total += int(d.Quantity * float64(price) / materialRemarks[d.StorageKey].StorageRatio)
			} else {
				total += int(d.Quantity * float64(price))
			}
		}
		scheduleOrder := model.ScheduleOrder{
			ID:             0,
			OrderNumber:    resp.OrderNumber,
			InWarehouseID:  req.Id,
			OutWarehouseID: req.OutWarehouseId,
			SgID:           sgId,
			Status:         constant.ScheduleOrderCreated,
			DetailCount:    uint32(len(req.Details)),
			Total:          uint64(total),
			Applier:        empId,
			ApplierName:    req.WebToken.EmpName,
			Reason:         "",
			ApplyTime:      now,
		}
		if err = s.dao.Create(ctx, &scheduleOrder); err != nil {
			return err
		}

		// create details
		for _, d := range req.Details {
			switch d.StorageType {
			case inventory.StorageType_MATERIAL:
				remark = materialRemarks[d.StorageKey]
			default:
				var ok bool
				remark, ok = objectRemarks[d.StorageKey]
				if !ok {
					err = constant.ErrSystemError
					return
				}
			}
			if remarkJson, err = json.Marshal(model.StorageSnapShotRemark{
				SnapShotRemark: remark.SnapShotRemark,
				StorageUnit:    remark.StorageUnit,
				StorageRatio:   remark.StorageRatio,
			}); err != nil {
				return
			}
			price, _ := priceMap[d.StorageKey]
			if err = s.dao.Create(ctx, &model.ScheduleDetail{
				OrderID:       scheduleOrder.ID,
				StorageType:   uint8(d.StorageType),
				StorageKey:    d.StorageKey,
				Remark:        orm.JSON(remarkJson),
				ApplyQuantity: d.Quantity,
				Price:         price,
				CreateTime:    now,
				UpdateTime:    now,
			}); err != nil {
				return
			}
		}
		return

	})
	return
}

// ReviewSchedule 调出审核
func (s *Service) ReviewSchedule(ctx context.Context, req *inventory.ReviewScheduleRequest) (resp *inventory.DefaultResp, err error) {
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	resp = &inventory.DefaultResp{
		Status: true,
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		var (
			order           *model.ScheduleOrder
			scheduleDetails []*model.ScheduleDetail
		)
		if _, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.ScheduleOut); err != nil {
			return
		}
		if order, err = s.dao.GetScheduleOrder(ctx, sgId, req.OrderNumber); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrOrderNotExist
			}
			return
		}
		if order.Status != constant.ScheduleOrderCreated {
			err = constant.ErrNeedRefresh
			return
		}
		if order.OutWarehouseID != req.Id {
			err = constant.ErrPermissionError
			return
		}
		now := time.Now()
		order.Reviewer = empId
		order.ReviewerName = req.WebToken.EmpName
		order.SendTime = &now
		if req.Status == 0 {
			order.Status = constant.ScheduleOrderRejected
			if err = s.dao.Save(ctx, order); err != nil {
				return
			}
			return
		} else {
			if len(req.Details) == 0 {
				err = ecode.BadRequest("明细数量不允许为0")
				return
			}
			if scheduleDetails, err = s.dao.FindScheduleOrderDetails(ctx, []uint64{order.ID}); err != nil {
				return
			}
			if len(scheduleDetails) != len(req.Details) {
				err = ecode.BadRequest("审核明细与申请不一致")
				return
			}
			storageMap := make(model.StorageMap)
			storageKeyMap := make(util.StringBoolMap)
			materialIDMap := make(util.UInt64BoolMap)
			scheduleRemarks := make(map[string]*model.StorageSnapShotRemark)
			var storages []*model.Storage
			for index, d := range scheduleDetails {
				detail := req.Details[index]
				if detail.StorageType != inventory.StorageType(d.StorageType) || d.StorageKey != detail.StorageKey {
					err = ecode.BadRequest("审核明细与申请不一致")
					return
				}
				if detail.Quantity < 0 {
					err = ecode.BadRequest("调拨审批数量不能小于0")
					return
				}
				storageKeyMap[d.StorageKey] = true
				remark := &model.StorageSnapShotRemark{}
				if err = json.Unmarshal(d.Remark, remark); err != nil {
					return
				}
				scheduleRemarks[d.StorageKey] = remark
			}
			if storages, err = s.dao.FindWarehouseStoragesByStorageKeys(ctx, req.Id, storageKeyMap.GetKeys()); err != nil {
				return
			}
			storageMap.InitStorageMap(storages)
			for index, d := range scheduleDetails {
				if storage, ok := storageMap[d.StorageKey]; ok {
					if req.Details[index].Quantity > storage.Quantity-storage.FreezeQuantity {
						err = formatStorageUnavailableError(storage)
						return
					}
				} else {
					remark := model.StorageSnapShotRemark{}
					if err = json.Unmarshal(d.Remark, &remark); err != nil {
						return err
					}
					err = ecode.AppendMessage(constant.ErrStorageUnavailable,
						fmt.Sprintf("商品%s没有找到对应库存数据", remark.Name))
					return
				}
			}
			total := 0
			for _, d := range req.Details {
				if d.StorageType == inventory.StorageType_MATERIAL {
					total += int(d.Quantity * float64(d.Price) / scheduleRemarks[d.StorageKey].StorageRatio)
				} else {
					total += int(d.Quantity * float64(d.Price))
				}
			}
			order.Total = uint64(total)
			order.Status = constant.ScheduleOrderAudited
			if err = s.dao.Save(ctx, order); err != nil {
				return
			}
			for index, d := range scheduleDetails {
				detail := req.Details[index]
				d.SendQuantity = detail.Quantity
				d.Price = detail.Price
				d.UpdateTime = now
				if err = s.dao.Save(ctx, d); err != nil {
					return
				}
				var affected bool
				if affected, err = s.dao.DecreaseStorage(ctx, req.Id, d.StorageKey, d.SendQuantity, now); err != nil {
					return
				}
				if !affected {
					delete(storageKeyMap, d.StorageKey)
				} else if inventory.StorageType(d.StorageType) == inventory.StorageType_MATERIAL {
					materialIDMap[*storageMap[d.StorageKey].MaterialID] = true
				}
				// 更新日结记录
				if err = s.dao.Create(ctx, &model.StatsDaily{
					RecordDate:    now,
					ActionType:    constant.ActionScheduleOut,
					WarehouseID:   req.Id,
					StorageKey:    d.StorageKey,
					StorageType:   d.StorageType,
					StorageName:   scheduleRemarks[d.StorageKey].Name,
					Quantity:      -d.SendQuantity,
					Price:         int64(d.Price),
					Amount:        -int64(float64(detail.Price) * detail.Quantity / scheduleRemarks[d.StorageKey].StorageRatio),
					OperationUnit: scheduleRemarks[d.StorageKey].StorageUnit,
				}); err != nil {
					return
				}
			}
		}
		return
	})
	return
}

// ReceiveSchedule 入库
func (s *Service) ReceiveSchedule(ctx context.Context, req *inventory.ReceiveScheduleRequest) (resp *inventory.DefaultResp, err error) {
	var (
		inWarehouse                    *model.Warehouse
		order                          *model.ScheduleOrder
		scheduleDetails                []*model.ScheduleDetail
		objectKeys                     []string
		objectRemarks, materialRemarks map[string]*model.FullRemark
	)
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if len(req.Details) == 0 {
		err = ecode.BadRequest("明细数量不允许为0")
		return
	}
	if order, err = s.dao.GetScheduleOrder(ctx, sgId, req.OrderNumber); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = constant.ErrOrderNotExist
		}
		return
	}
	if order.Status != constant.ScheduleOrderAudited {
		err = constant.ErrNeedRefresh
		return
	}
	if scheduleDetails, err = s.dao.FindScheduleOrderDetails(ctx, []uint64{order.ID}); err != nil {
		return
	}
	materialIDMap := make(util.UInt64BoolMap)
	for index, d := range scheduleDetails {
		detail := req.Details[index]
		if detail.StorageType != inventory.StorageType(d.StorageType) || d.StorageKey != detail.StorageKey {
			err = ecode.BadRequest("入库明细与申请不一致")
			return
		}
		if detail.Quantity > d.SendQuantity {
			remark := model.StorageSnapShotRemark{}
			if err = json.Unmarshal(d.Remark, &remark); err != nil {
				return
			}
			err = ecode.AppendMessage(constant.ErrStorageUnavailable,
				fmt.Sprintf("商品%s入库数量不能超过调拨出库数量", remark.Name))
			return
		}
		if detail.Quantity < 0 {
			err = ecode.BadRequest("入库数量不能小于0")
			return
		}
		if inventory.StorageType(d.StorageType) != inventory.StorageType_MATERIAL {
			objectKeys = append(objectKeys, d.StorageKey)
		} else {
			var materialID int
			if materialID, err = strconv.Atoi(strings.Split(d.StorageKey, "-")[1]); err != nil {
				return
			} else {
				materialIDMap[uint64(materialID)] = true
			}
		}

	}
	// build remarks
	if len(objectKeys) != 0 {
		if objectRemarks, err = s.downstream.BuildObjectRemarks(ctx, sgId, objectKeys); err != nil {
			return
		}
		if len(objectRemarks) != len(objectKeys) {
			err = ecode.AppendMessage(constant.ErrRemoteAPIFailed, fmt.Sprintf("web-pos服务调用失败: %s", err))
			return
		}
	}
	if len(materialIDMap) != 0 {
		if materialRemarks, err = s.dao.BuildMaterialsRemarks(ctx, sgId, materialIDMap.GetKeys()); err != nil {
			return
		}
		if len(materialRemarks) != len(materialIDMap) {
			err = constant.ErrSystemError
			return
		}
	}
	resp = &inventory.DefaultResp{
		Status: true,
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		if inWarehouse, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.ScheduleIn); err != nil {
			return
		}
		if order.InWarehouseID != req.Id {
			err = constant.ErrPermissionError
			return
		}
		now := time.Now()
		order.Status = constant.ScheduleOrderStored
		order.Receiver = empId
		order.ReceiverName = req.WebToken.EmpName
		order.ReceiveTime = &now
		total := 0
		for index, d := range scheduleDetails {
			total += int(req.Details[index].Quantity * float64(d.Price))
		}
		order.Total = uint64(total)
		if err = s.dao.Save(ctx, order); err != nil {
			return
		}
		affectedStorageKeyMap := make(util.StringBoolMap)
		affectedMaterialIDMap := make(util.UInt64BoolMap)
		for index, d := range scheduleDetails {
			d.ReceiveQuantity = req.Details[index].Quantity
			d.UpdateTime = now
			if err = s.dao.Save(ctx, d); err != nil {
				return
			}
			if order.Status == constant.ScheduleOrderStored && d.ReceiveQuantity > 0 {
				var (
					remark                       *model.FullRemark
					remarkJson                   json.RawMessage
					snapShotRemark               model.StorageSnapShotRemark
					prodPointer, materialPointer *uint64
				)
				affectedStorageKeyMap[d.StorageKey] = true
				if inventory.StorageType(d.StorageType) == inventory.StorageType_MATERIAL {
					remark = materialRemarks[d.StorageKey]
					tmpID, _ := strconv.Atoi(d.StorageKey[2:])
					materialID := uint64(tmpID)
					affectedMaterialIDMap[materialID] = true
					materialPointer = &materialID
				} else {
					remark = objectRemarks[d.StorageKey]
					if inventory.StorageType(d.StorageType) == inventory.StorageType_PROD {
						tmpID, _ := strconv.Atoi(strings.Split(d.StorageKey, "-")[0])
						prodID := uint64(tmpID)
						prodPointer = &prodID
					}
				}
				if remarkJson, err = json.Marshal(model.StorageRealTimeRemark{
					Remark:        remark.Remark,
					Type:          remark.TypeID,
					StorageUnit:   remark.StorageUnit,
					StorageRatio:  remark.StorageRatio,
					PurchaseUnit:  remark.PurchaseUnit,
					PurchaseRatio: remark.PurchaseRatio,
				}); err != nil {
					return
				}
				// 调入模式时，如果不存在库存记录需要新增
				if err = s.dao.CreateOrIncreaseStorage(ctx, &model.Storage{
					WarehouseID:    req.Id,
					StorageKey:     d.StorageKey,
					StorageType:    d.StorageType,
					Quantity:       d.ReceiveQuantity,
					FreezeQuantity: 0,
					StorageMode:    inWarehouse.StorageMode,
					ProdID:         prodPointer,
					MaterialID:     materialPointer,
					Remark:         orm.JSON(remarkJson),
					UpdateTime:     now,
				}); err != nil {
					return
				}
				// 更新日结记录
				if err = json.Unmarshal(d.Remark, &snapShotRemark); err != nil {
					return
				}
				if err = s.dao.Create(ctx, &model.StatsDaily{
					RecordDate:    now,
					ActionType:    constant.ActionScheduleIn,
					WarehouseID:   req.Id,
					StorageKey:    d.StorageKey,
					StorageType:   d.StorageType,
					StorageName:   snapShotRemark.Name,
					Quantity:      d.ReceiveQuantity,
					Price:         int64(d.Price),
					Amount:        -int64(float64(d.Price) * d.ReceiveQuantity / snapShotRemark.StorageRatio),
					OperationUnit: snapShotRemark.StorageUnit,
				}); err != nil {
					return
				}
			}
		}
		return
	})
	return
}
